/*
 * This code is based on the article by Jim McBeath at
 * http://jim-mcbeath.blogspot.com/2008/09/scala-parser-combinators.html
 */
package mathFrame

import scala.util.parsing.combinator.syntactical._
import scala.util.parsing.combinator.lexical.StdLexical

class ExprLexical extends StdLexical {
  override def token: Parser[Token] = floatingToken | super.token

  def floatingToken: Parser[Token] =
    rep1(digit) ~ optFraction ~ optExponent ^^
      {case intPart ~ frac ~ exp => NumericLit(
        (intPart mkString "") :: frac :: exp :: Nil mkString "")}

  def chr(c: Char) = elem("", ch => ch == c )
  
  def sign = chr('+') | chr('-')
  
  def optSign = opt(sign) ^^ {
    case None => ""
    case Some(sign) => sign
  }
  
  def fraction = '.' ~ rep(digit) ^^ {
    case dot ~ ff => dot :: (ff mkString "") :: Nil mkString ""
  }
  
  def optFraction = opt(fraction) ^^ {
    case None => ""
    case Some(fraction) => fraction
  }
  
  def exponent = (chr('e') | chr('E')) ~ optSign ~ rep1(digit) ^^ {
    case e ~ optSign ~ exp => e :: optSign :: (exp mkString "") :: Nil mkString ""
  }
  
  def optExponent = opt(exponent) ^^ {
    case None => ""
    case Some(exponent) => exponent
  }
}

object JimParsers extends StandardTokenParsers {
  override val lexical = new ExprLexical
  
  private var identifiers: Map[String, Value] = Map()
  private var parameters: Map[String, Parameter] = null
  private var functions: Map[String, (Value*) => MateFunction] = Map()
  private var operators: Map[Char, (Value*) => BinaryOperator] = Map()

  lexical.delimiters ++= List("+", "-", "*", "/", "(", ")", "=")
  lexical.reserved ++= List("const", "var", "def")
  
  def value = numericLit ^^ {s => Number(s.toDouble)}
  
  def parens:Parser[Value] = "(" ~> expr <~ ")"
  
  def uMinus: Parser[Value] = "-" ~> term ^^ {s => UM(s)}
  
  val existingIdentifier: Parser[Value] = new Parser[Value] {
    def apply(in: Input) = {
      val result = ident(in)
      val name = result.get
      parameters.get(name) match {
        case Some(param) => new Success(param, result.next)
        case _ => {
          identifiers.get(name) match {
            case Some(vari) => new Success(vari, result.next)
            case _ => 
              new Failure("Identifier '" + name + "' isn't defined.", result.next)
          }
        }
      }
    }
  }
  
  val existingFunction: Parser[(Value*) => MateFunction] = 
    new Parser[(Value*) => MateFunction] {
      def apply(in: Input) = {
        val result = ident(in)
        val name = result.get
        functions.get(name) match {
          case Some(func) => new Success(func, result.next)
          case _ => 
            new Failure("Function '" + name + "' isn't defined.", result.next)
        }
      }
    }
  
  def functionCall: Parser[Value] = 
    existingFunction ~ "(" ~ expr <~ ")" ^^ {s => s._1._1(s._2)}
  
  def term = (value | parens | uMinus | functionCall | existingIdentifier)
  
  def binaryOp(level: Int): Parser[((Value, Value) => Value)] = (
    level match {
      case 1 =>
        "+" ^^^ {(a: Value, b: Value) => Sum(a,b)} |
        "-" ^^^ {(a: Value, b: Value) => Sub(a,b)}
      case 2 =>
        "*" ^^^ {(a: Value, b: Value) => Prod(a,b)} |
        "/" ^^^ {(a: Value, b: Value) => Div(a,b)}
    }
  )
  
  val minPrec = 1
  
  val maxPrec = 2
  
  def binary(level: Int): Parser[Value] = {
    if (level > maxPrec) term
    else binary(level + 1) * binaryOp(level)
  }
  
  def expr = (binary(minPrec) | term)
  
  val newIdentifier = new Parser[String] {
    def apply(in: Input) = {
      val result = ident(in)
      val name = result.get
      if (identifiers contains name)
        new Failure("Identifier '" + name + "' already defined.", result.next)
      else result
    }
  }
  
  val newParameter: Parser[Parameter] = new Parser[Parameter] {
    def apply(in: Input) = {
      val result = ident(in)
      val name = result.get
      parameters.get(name) match {
        case Some(parameter) => 
          new Failure("Parameter '" + name + "' already defined.", result.next)
        case _ => {
            val parameter = new Parameter(name)
            parameters += (name -> parameter)
            new Success(parameter, result.next)
        }
      }
    }
  }
  
  private def constant: Parser[Value] = 
    "const" ~> newIdentifier ~ "=" ~ value ^^ {
      s => {
        val name = s._1._1
        val number = s._2
        val constant = new Constant(name, number)
        identifiers += (name -> constant)
        constant
      }
    }
  
  private def variable: Parser[Value] = 
    "var" ~> newIdentifier ~ opt("=" ~> value) ^^ {
      s => {
        def createVariable(name: String) = {
          if (s._2 == None) new Variable(name)
          else {
            val number = s._2.get
            new Variable(name, number)
          }
        }

        val name = s._1
        val variable = createVariable(name)
        identifiers += (name -> variable)
        variable
      }
    }
  
  private def function: Parser[Value] = 
    "def" ~> newIdentifier ~ "(" ~ newParameter ~ ")" ~ "=" ~ expr ^^ {
      s => {
        val name = s._1._1._1._1._1
        val parameter = s._1._1._1._2
        val func = new UserFunction1P(name, parameter, s._2)
        functions += (name -> func.createComposite)
        func
      }
    }
  
  private def init = {
    parameters = Map()
    success()
  }
  
  private def definition = constant | variable | (init ~> function)
  
  def parse(s: String) = {
    parameters = Map()
    val tokens = new lexical.Scanner(s)
    phrase(definition)(tokens)
  }
  
  def parseExpr(s: String) = {
    parameters = Map()
    val tokens = new lexical.Scanner(s)
    phrase(expr)(tokens)
  }
  
  def parseExpr(s: String, x: Parameter) = {
    parameters = Map(x.name -> x)
    val tokens = new lexical.Scanner(s)
    phrase(expr)(tokens)
  }
  
  def apply(s: String): Value = {
    parse(s) match {
      case Success(tree, _) => tree
      case e: NoSuccess => throw new IllegalArgumentException("Bad syntax " + s)
    }
  }
  
  def test(s: String) = {
    parse(s) match {
      case Success(tree, _) =>
        println("Tree: " + tree)
        println("Value: " + tree.value)
    }
  }

  def testExpr(s: String) = {
    parseExpr(s) match {
      case Success(tree, _) =>
        println("Tree: " + tree)
        println("Value: " + tree.value)
    }
  }

  def testFail(s: String) = {
    parse(s) match {
      case Failure(msg, _) =>
        println("\"" + s + "\" failed as expected")
    }
  }

  def addFunction(name: String, functionCreator: ((Value*) => MateFunction)) = {
    functions += (name -> functionCreator)
  }
  
  def addOperator(name: Char, operatorCreator: ((Value*) => BinaryOperator)) = {
    operators += (name -> operatorCreator)
  }
  
  def addVariable(name: String, variable: Variable) = {
    identifiers += (name -> variable)
  }

  def addConstant(name: String, constant: Constant) = {
    identifiers += (name -> constant)
  }
}